from __future__ import absolute_import

import threading
import logging
import traceback
import copy
from datetime import datetime

import yaml
from rest_framework import status
from django.core.cache import cache
from django.utils.deprecation import MiddlewareMixin

from .models import EventData, AlarmData
from .apps import TraceConfig

logger = logging.getLogger(__name__)


def thread_save_model(db_model, db_data):
    """异步将记录保存在数据库中
    :param db_model: 数据库中的数据表名
    :param db_data: 保存的数据记录
    :return
    """
    db = db_model(**db_data)
    asy_save = threading.Thread(target=db.save)
    try:
        asy_save.start()
    except Exception as e:
        logger.error("db save failed: %s, %s", repr(e), traceback.format_exc())
    return

def get_template(template_path):
    """根据模板路径获取模板的内容
    :param template_path: 模板文件的路径
    :return -> dict
    """
    with open(template_path, 'r', encoding='utf8') as fp:
        template = yaml.safe_load(fp.read())

    return template


def save_alarm_record(template, *args, **kwargs):
    """根据模板与参数在数据库中保存记录
    :param template: 模板
    :param args:
    :param kwargs:
    :return
    """
    record = template.copy()
    for key in kwargs:
        if key not in record:
            continue
        value = kwargs[key]
        if isinstance(value, (list, dict)):
            record[key] = value.copy()
        else:
            record[key] = value
    thread_save_model(AlarmData, record)
    return


def is_trigger(template, cache_content, rule, rule_name, alarm_type):
    """ 判断缓存中的内容是否能触发告警
    :param template: 告警的模板
    :param cache_rule: 该条告警缓存的内容
    :param rule: 该条告警的规则
    :param rule_name: 该条告警名
    :param alarm_type: 告警类型
    :return: boolean
    """
    if "cycle" not in rule:
        return True
    cache_rule = cache_content.get(rule_name)
    last_alarm_time = datetime.strptime(cache_rule["last_alarm_time"], "%Y-%m-%d %H:%M:%S.%f")
    seconds = (datetime.today() - last_alarm_time).seconds
    if seconds < rule["cycle"]:
        return False
    if alarm_type == "EventAlarm":
        if cache_rule["from_last_alarm"] < rule["trigger"]:
            return False
        generate_alarm_record(template, cache_content, rule_name, alarm_type=alarm_type)
        cache_rule["from_last_alarm"] = 0
        if rule_name != "login_from_different_location":
            cache_rule["details"].clear()
        cache_rule["last_alarm_time"] = str(datetime.today())
        return True

    cache_rule["last_alarm_time"] = str(datetime.today())
    generate_alarm_record(template, cache_content, rule_name, alarm_type)
    return True


def generate_alarm_record(template, cache_content, rule_name, alarm_type):
    """生成并保存告警记录
    :param template: 告警的模板
    :param cache_content: 缓存用户可能触发告警的操作
    :param rule_name: 告警规则名
    :param alarm_type: 告警类型
    :return
    """
    alarm_rule = template.get("alarm_rules")[alarm_type][rule_name]
    level = alarm_rule["level"]
    state = 1
    if rule_name == "login_from_different_location":
        details = cache_content[rule_name]["details"][-1:]
    else:
        details = cache_content[rule_name]["details"]
    save_alarm_record(template.get("alarm"), alarm_name=rule_name,
        alarm_type=alarm_type, alarm_source=cache_content["source"],
        state=state, level=level, last_operate_time=cache_content[rule_name]["last_alarm_time"],
        details=details)
    return


def generate_alarm_cache(template, source, alarm_type):
    """生成告警初始缓存
    :param template: 告警模板
    :param source: 告警来源
    :param alarm_type: 告警类型
    :return
    """
    cache_content = {}
    alarm_cache_template = template.get("alarm_cache_template")
    alarm_cache_config = template.get("alarm_cache_conf")
    alarm_rules = template.get("alarm_rules")[alarm_type]
    creat_time = str(datetime.today())

    cache_content["create_time"] = creat_time
    cache_content["source"] = source

    filter_alarm = AlarmData.objects.filter(alarm_name="login_from_different_location")
    ips = []
    for detail in filter_alarm:
        ips.extend(detail.details)

    for cache_rule in alarm_rules:
        if not isinstance(alarm_rules[cache_rule], dict):
            continue
        cache_content[cache_rule] = copy.deepcopy(alarm_cache_template["info"])
        cache_content[cache_rule]["last_alarm_time"] = alarm_cache_config.get("init_time")
        if cache_rule == "login_from_different_location":
            filter_alarm = AlarmData.objects.filter(alarm_name=cache_rule)
            for alarm_record in filter_alarm:
                cache_content[cache_rule]["details"].extend(alarm_record.details)
            cache_content[cache_rule]["details"] = list(set(cache_content[cache_rule]["details"]))
    return cache_content


class EventTraceMiddleware(MiddlewareMixin):

    template = get_template(TraceConfig.template_event)
    event_trace = None
    method = None
    app_name = None
    view_name = None
    app_mapping = None

    def get_single_id_name(self, request):
        resource_names = "--"
        resource_ids = []
        if "pk" in request.POST:
            resource_ids.append(request.POST.get("pk"))
        elif "pk" in request.resolver_match.kwargs:
            resource_ids.append(request.resolver_match.kwargs["pk"])
        elif "ids" in request.POST:
            ids = request.POST.get("ids")
            resource_ids = ids if isinstance(ids, list) else ids.split(",")
        else:
            resource_ids = "--"

        return resource_ids, resource_names

    def get_batch_id_name(self, response):
        resource_ids = []
        resource_names = []
        if "data" in response.data:
            # 返回的数据体有多个数据
            if "data_list" in response.data["data"]:
                resource_ids = []
                resource_names = []
                for resource_data in response.data["data"]["data_list"]:
                    resource_ids.append(resource_data["id"])
                    resource_names.append(resource_data["name"])

            # 返回的数据体只有一个数据
            elif "id" in response.data["data"] and "name" in response.data["data"]:
                resource_ids.append(response.data["data"]["id"])
                resource_names.append(response.data["data"]["name"])
        return resource_ids, resource_names

    def get_event_id_name_details(self, request, response):
        """获取数据操作涉及的id/name/详情"""
        resource_ids = []
        resource_names = []
        details = {}
        if hasattr(response, "data"):
            try:
                resource_ids, resource_names = self.get_batch_id_name(response)
            except Exception as e:
                logger.error("names and ids get faild: %s, %s", repr(e), traceback.format_exc())
            try:
                details = response.data
            except Exception as e:
                details = "--"
                logger.error("db details get faild: %s, %s", repr(e), traceback.format_exc())
        if len(resource_ids) == 0 and len(resource_names) == 0:
            resource_ids, resource_names = self.get_single_id_name(request)

        return resource_ids, resource_names, details

    def get_response_state(self, response):
        if not hasattr(response, "status_code"):
            return "failed"
        if status.is_success(response.status_code):
            return "succeed"
        return "failed"

    def process_request(self, request):
        self.event_trace = self.template.get("event").copy()
        self.method = (request.method).lower()
        if request.user.username:
            self.event_trace["username"] = request.user.username
        elif "username" in request.POST:
            self.event_trace["username"] = request.POST.get('username')
        self.event_trace["create_time"] = str(datetime.today())

        return

    def process_response(self, request, response):
        self.app_name = request.resolver_match._func_path.split('.')[0]
        self.view_name = request.resolver_match._func_path.split('.')[-1]
        if request.user.username:
            self.event_trace["username"] = request.user.username
        elif hasattr(response, "data"):
            if "username" in response.data:
                self.event_trace["username"] = response.data.get('username')
        if self.method not in self.template["event_data_allow"]["method"]:
            logger.warning("the method: %s not in event_data_allow", self.method)
            return response

        if self.app_name not in self.template["event_mappings"]:
            logger.warning("the app %s not in event App-list", self.app_name)
            return response

        self.app_mapping = self.template["event_mappings"][self.app_name]

        if self.view_name in self.app_mapping:
            if self.method not in self.app_mapping[self.view_name]:
                logger.warning("the view-method %s not in event View-Method-List", self.method)
                return response

            operate = self.app_mapping[self.view_name][self.method]["operate"]
            self.event_trace["level"] = self.app_mapping[self.view_name][self.method]["level"]
            enable = self.app_mapping[self.view_name][self.method]["enable"]

        else:
            logger.warning("the view %s not in event trace list", self.view_name)
            return response

        if not enable:
            logger.warning("the view %s not enable", self.view_name)
            return response

        self.event_trace["event_source"] = self.app_name
        self.event_trace["event_type"] = self.app_mapping["event_type"]
        hyphen = ''
        if self.event_trace["event_type"] in self.template["event_data_allow"]["record_resource_type"]:
            self.event_trace["resource_type"] = self.app_mapping[self.view_name]["db_table"]
            hyphen = '-'

        self.event_trace["event_name"] = f'{operate}{hyphen}{self.event_trace["resource_type"]}' \
            if hyphen else f'{operate}'

        self.event_trace["resource_id"], self.event_trace["resource_name"], self.event_trace["details"] = \
            self.get_event_id_name_details(request, response)

        self.event_trace["state"] = self.get_response_state(response)

        thread_save_model(EventData, self.event_trace)
        return response


class AlarmTraceMiddleware(MiddlewareMixin):

    template = get_template(TraceConfig.template_alarm)
    alarm_cache_template = template.get("alarm_cache_template")
    alarm_cache_config = template.get("alarm_cache_conf")
    alarm_rules = template.get("alarm_rules")

    def process_request(self, request):
        return


    def process_response(self, request, response):
        method = (request.method).lower()
        if method not in self.template["alarm_data_allow"]["method"]:
            logger.info("the method: %s not in alarm_data_allow", method)
            return response

        username = ""
        if request.user.username:
            username = request.user.username
        elif hasattr(response, "data"):
            if "username" in response.data:
                username = response.data.get('username')
        if not username:
            logger.warning("the user name is None")
            return response

        cache_name = self.alarm_cache_config.get("cache_name").format(username, "event")
        if not cache.get(cache_name):
            cache_content = generate_alarm_cache(self.template, username, "EventAlarm")
        else:
            cache_content = cache.get(cache_name)
        self.update_EventAlarm_cache_info(request, response, cache_content)
        cache.set(cache_name, cache_content, timeout=self.alarm_cache_config.get("expiration"))

        return response


    def update_EventAlarm_cache_info(self, request, response, cache_content):
        """更新告警缓存内容"""
        if "EventAlarm" not in self.template["alarm_data_allow"]["rule_type"]:
            logger.info("the alarm rule_type: EventAlarm not in alarm_data_allow")
            return

        event_alarm_rules = self.alarm_rules.get("EventAlarm")
        app_name = request.resolver_match._func_path.split('.')[0]
        view_name = request.resolver_match._func_path.split('.')[-1]
        for rule_name in event_alarm_rules:
            if not isinstance(event_alarm_rules[rule_name], dict):
                continue
            if not event_alarm_rules[rule_name]["enable"]:
                continue
            rule = event_alarm_rules[rule_name]
            if app_name not in rule["monitor_apps"]:
                continue
            # 监测的`app`的`view`为空则代表需要监控该`app`的所有`view`
            if len(rule["monitor_apps"][app_name]) > 0 and \
                view_name not in rule["monitor_apps"][app_name]:
                return

            getattr(self, f"update_{rule_name}_info")(request, response,
                cache_content, rule_name)


    def update_alarm_rule_cache_info(self, request, response, cache_content, rule_name, incr=1, detail=None):
        """更新某一条告警记录的缓存"""
        cache_rule = cache_content[rule_name]
        rule = self.alarm_rules["EventAlarm"][rule_name]
        detail = self.generate_alarm_detail(request, response) if detail is None else detail
        cache_rule["from_last_alarm"] += incr
        cache_rule["total"] += incr
        if isinstance(detail, list):
            cache_rule["details"].extend(detail)
        else:
            cache_rule["details"].append(detail)

        trigger = is_trigger(self.template, cache_content, rule, rule_name,
            alarm_type="EventAlarm")
        if trigger:
            logger.warning("The alarm: %s has been recorded", rule_name)
        return


    def update_multiple_login_failed_info(self, request, response, cache_content, rule_name):
        """更新告警规则：多次登录失败 缓存"""
        if hasattr(response, "status_code"):
            if status.is_success(response.status_code):
                return

        self.update_alarm_rule_cache_info(request, response, cache_content, rule_name)
        return


    def update_login_from_different_location_info(self, request, response, cache_content, rule_name):
        """更新告警规则：异地登录 缓存"""
        cache_rule = cache_content[rule_name]
        req_ip = ""
        if "HTTP_X_FORWARDED_FOR" in request.META:
            req_ip = request.META.get("HTTP_X_FORWARDED_FOR")
        elif "REMOTE_ADDR" in request.META:
            req_ip = request.META.get("REMOTE_ADDR")
        if not req_ip:
            logger.warning("can not get request ip")
            return

        if req_ip in cache_rule["details"]:
            return

        cache_rule["details"].append(req_ip)
        self.update_alarm_rule_cache_info(request, response, cache_content, rule_name,
            detail=req_ip)
        return


    def update_multiple_service_invoke_failed_info(self, request, response, cache_content, rule_name):
        """更新告警规则：多次服务调用失败 缓存"""
        if hasattr(response, "status_code"):
            if status.is_success(response.status_code):
                return

        self.update_alarm_rule_cache_info(request, response, cache_content, rule_name)
        return


    def update_data_deletion_info(self, request, response, cache_content, rule_name):
        """更新告警规则：数据删除 缓存"""
        method = (request.method).lower()
        if "delete" != method:
            return
        if response.status_code != 204:
            return
        if "ids" in request.POST:
            delete_id_list = request.POST.get("ids").split(',')
            incr = len(delete_id_list)
        else:
            delete_id_list = str(request.resolver_match.kwargs["pk"])
            incr = 1
        self.update_alarm_rule_cache_info(request, response, cache_content, rule_name,
            incr=incr, detail=delete_id_list)
        return


    def generate_alarm_detail(self, request, response):
        """获取告警记录的详情"""
        url_path = f"{request.method} {request.resolver_match._func_path}"
        res = {
            "code": response.status_code,
            "url_path": url_path
        }
        if hasattr(response, "data"):
            res.update({"data": response.data})
        if hasattr(response, "status_text"):
            res.update({"text": response.status_text})
        return res
        